{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "原始连接：https://tensorflow.google.cn/tutorials/text/image_captioning      \n",
    "参考论文：https://arxiv.org/pdf/1502.03044.pdf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.utils import shuffle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "import numpy as np\n",
    "import os\n",
    "import time\n",
    "import json\n",
    "import pickle\n",
    "from glob import glob\n",
    "from PIL import Image"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# MS-COCO 数据集\n",
    "82000 张图片，每张图片至少有5个标注说明"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "annotation_folder = \"../datasets/annotations/\"\n",
    "if not os.path.exists(os.path.abspath('.') + annotation_folder):\n",
    "    annotation_zip = tf.keras.utils.get_file(\n",
    "        'captions.zip',\n",
    "        cache_subdir=os.path.abspath(\".\"),\n",
    "        origin=\n",
    "        'http://images.cocodataset.org/annotations/annotations_trainval2014.zip',\n",
    "        extract=True)\n",
    "    annotation_file = os.path.dirname(\n",
    "        annotation_zip) + '../datasets/annotations/captions_train2014.json'\n",
    "    os.remove(annotation_zip)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "image_folder = '/train2014/'\n",
    "if not os.path.exists(os.path.abspath(\".\") + image_folder):\n",
    "    image_zip = tf.keras.utils.get_file('train2014.zip',\n",
    "                                        cache_subdir=os.path.abspath(\".\"),\n",
    "                                        extract=True)\n",
    "    PATH = os.path.dirname(image_zip) + image_folder\n",
    "    os.remove(image_zip)\n",
    "else:\n",
    "    PATH = os.path.abspath(\".\") + image_folder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(annotation_file, 'r') as f:\n",
    "    annotations = json.load(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ">标注的文本进行预处理，添加 `<start>,<end>` 标签；一张图片路径对应一个标注序列"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "all_captions = []  # 标注序列\n",
    "all_image_name_vector = []  # 图片路径\n",
    "for annot in annotations['annotations']:\n",
    "    caption = \"<start>\" + annot[\"caption\"] + \"<end>\"\n",
    "    image_id = annot[\"image_id\"]\n",
    "    full_coco_image_path = PATH + \"COCO_train2014\" + \"%012d.jpg\" % (image_id)\n",
    "    all_image_name_vector.append(full_coco_image_path)\n",
    "    all_captions.append(caption)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_captions, img_name_vector = shuffle(all_captions,\n",
    "                                          all_image_name_vector,\n",
    "                                          random_state=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_examples = 30000\n",
    "train_captions = train_captions[:num_examples]  # 训练标签\n",
    "img_name_vector = img_name_vector[:num_examples]  # 训练数据"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# InceptionV3 模型预处理图片"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_image(image_path):\n",
    "    img = tf.io.read_file(image_path)\n",
    "    img = tf.image.decode_jpeg(img, channels=3)\n",
    "    img = tf.image.resize(img, (299, 299))\n",
    "    img = tf.keras.applications.inception_v3.preprocess_input(img)\n",
    "    return img, image_path"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> - 形状变为 299px * 299 px\n",
    "- inception_v3 的 preprocess_input 方法，标准化像素数值到 -1 ~ 1 之间"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 初始化InceptionV3模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "image_model = tf.keras.applications.InceptionV3(include_top=False,\n",
    "                                                weights='imagenet')\n",
    "new_input = image_model.input\n",
    "hidden_layer = image_model.layers[-1].output\n",
    "image_features_extract_model = tf.keras.Model(new_input, hidden_layer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ">只利用 InceptionV3 的特征提取层"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# InceptionV3提取图片特征，并保存"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 图片数据管道\n",
    "encode_train = sorted(set(img_name_vector))\n",
    "image_dataset = tf.data.Dataset.from_tensor_slices(encode_train)\n",
    "image_dataset = image_dataset.map(\n",
    "    load_image, num_parallel_calls=tf.data.experimental.AUTOTUNE).batch(16)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tqdm import tqdm\n",
    "\n",
    "# 以批为单位，提取特征，并保存\n",
    "for img, path in tqdm(image_dataset):\n",
    "    batch_features = image_features_extract_model(img)\n",
    "    batch_features = tf.reshape(\n",
    "        batch_features, (batch_features.shape[0], -1, batch_features.shape[3]))\n",
    "    for bf, p in zip(batch_features, path):\n",
    "        path_of_feature = p.numpy().decode('utf-8')\n",
    "        np.save(path_of_feature, bf.numpy())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 预处理图片标注并向量化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 计算图片标注的最大长度\n",
    "def calc_max_length(tensor):\n",
    "    return max(len(t) for t in tensor)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 选择词典中的前 5000 个单词\n",
    "top_k = 5000\n",
    "tokenizer = tf.keras.preprocessing.text.Tokenizer(\n",
    "    num_words=top_k,\n",
    "    oov_token='<unk>',\n",
    "    filters='!\"$%&()*+.,-/:;=?@[\\]^_`{|}~ ')\n",
    "\n",
    "tokenizer.fit_on_texts(train_captions)\n",
    "\n",
    "# 文本向量化\n",
    "train_seqs = tokenizer.texts_to_sequences(train_captions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 填充标记\n",
    "tokenizer.word_index['<pad>'] = 0\n",
    "tokenizer.index_word[0] = '<pad>'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 填充成相同的长度\n",
    "cap_vector = tf.keras.preprocessing.sequence.pad_sequences(train_seqs,\n",
    "                                                           padding='post')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 最长长度，用来保存 注意力权重\n",
    "max_length = calc_max_length(train_seqs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 拆分数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "img_name_train, img_name_val, cap_train, cap_val = train_test_split(\n",
    "    img_name_vector, cap_vector, test_size=0.2, random_state=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 创建数据管道"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "BATCH_SIZE = 64\n",
    "BUFFER_SIZE = 1000\n",
    "embedding_size = 256\n",
    "units = 512\n",
    "vocab_size = top_k + 1\n",
    "num_steps = len(img_name_train) // BATCH_SIZE\n",
    "features_shape = 2048\n",
    "attention_features_shape = 64"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def map_func(img_name, cap):\n",
    "    img_tensor = np.load(img_name.decode('utf-8') + '.npy')\n",
    "    return img_tensor, cap"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dataset = tf.data.Dataset.from_tensor_slices(img_name_train, cap_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dataset = dataset.map(lambda item1, item2: tf.numpy_function(\n",
    "    map_func, [item1, item2], [tf.float32, tf.int32],\n",
    "    num_parallel_calls=tf.data.experimental.AUTOTUNE))\n",
    "\n",
    "dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE)\n",
    "\n",
    "dataset = dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 模型\n",
    "- 通过 InceptionV3 的低层卷积层提取特征，获得张量形状 (8,8,2048)\n",
    "- 现状改变为 （64，2048）\n",
    "- 上一步的张量传给 CNN编码器（单一的全连接层），\n",
    "- RNN 解码器对图片进行注意力计算，预测标签    \n",
    "<img src=\"../images/img_caption.PNG\" width=\"80%\">"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class BahdanauAttention(tf.keras.Model):\n",
    "    def __init__(self, units):\n",
    "        super(BahdanauAttention, self).__init__()\n",
    "        self.W1 = tf.keras.layers.Dense(units)\n",
    "        self.W2 = tf.keras.layers.Dense(units)\n",
    "        self.V = tf.keras.layers.Dense(1)\n",
    "\n",
    "    def call(self, features, hidden):\n",
    "        # features shape == (batch_size, 64, embedding_dim)\n",
    "        # hidden shape == (batch_size, hidden_size)\n",
    "        # hidden_with_time_axis shape == (batch_size, 1, hidden_size)\n",
    "        \n",
    "        hidden_with_time_axis = tf.expand_dims(hidden, 1)\n",
    "        \n",
    "        # score shape == (batch_size, 64, hidden_size)\n",
    "        score = tf.nn.tanh(self.W1(features) + self.W2(hidden_with_time_axis))\n",
    "        \n",
    "        # attention_weights shape == (batch_size, 64, 1)\n",
    "        attention_weights = tf.nn.softmax(self.V(score), axis=1)\n",
    "        \n",
    "        # context_vector shape after sum == (batch_size, hidden_size)\n",
    "        context_vector = attention_weights * features\n",
    "        context_vector = tf.reduce_sum(context_vector, axis=1)\n",
    "        return context_vector, attention_weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CNN_Encoder(tf.keras.Model):\n",
    "    def __init__(self, embedding_dim):\n",
    "        super(CNN_Encoder, self).__init__()\n",
    "        self.fc = tf.keras.layers.Dense(embedding_dim)\n",
    "\n",
    "    def call(self, x):\n",
    "        x = self.fc(x)\n",
    "        # shape  == (batch_size, 64, embedding_dim)\n",
    "        \n",
    "        x = tf.nn.relu(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class RNN_Decoder(tf.keras.Model):\n",
    "    def __init__(self, embedding_dim, units, vocab_size):\n",
    "        super(RNN_Decoder, self).__init__()\n",
    "        self.units = units\n",
    "        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)\n",
    "        self.gru = tf.keras.layers.GRU(self.units,\n",
    "                                       return_state=True,\n",
    "                                       return_sequences=True,\n",
    "                                       recurrent_initializer='glorot_uniform')\n",
    "        self.fc1 = tf.keras.layers.Dense(self.units)\n",
    "        self.fc2 = tf.keras.layers.Dense(vocab_size)\n",
    "        self.attention = BahdanauAttention(self.units)\n",
    "\n",
    "    def call(self, x, features, hidden):\n",
    "        # defining attention as a separate model\n",
    "        context_vector, attention_weights = self.attention(features, hidden)\n",
    "\n",
    "        # x shape after passing through embedding == (batch_size, 1, embedding_dim)\n",
    "        x = self.embedding(x)\n",
    "\n",
    "        # x shape after concatenation == (batch_size, 1, embedding_dim + hidden_size)\n",
    "        x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)\n",
    "\n",
    "        # passing the concatenated vector to the GRU\n",
    "        output, state = self.gru(x)\n",
    "\n",
    "        # shape == (batch_size, max_length, hidden_size)\n",
    "        x = self.fc1(output)\n",
    "\n",
    "        # x shape == (batch_size * max_length, hidden_size)\n",
    "        x = tf.reshape(x, (-1, x.shape[2]))\n",
    "\n",
    "        # output shape == (batch_size * max_length, vocab)\n",
    "        x = self.fc2(x)\n",
    "\n",
    "        return x, state, attention_weights\n",
    "\n",
    "    def reset_states(self, batch_size):\n",
    "        return tf.zeros((batch_size, self.units))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "encoder = CNN_Encoder(embedding_size)\n",
    "decoder = RNN_Decoder(embedding_size, units, vocab_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = tf.keras.optimizers.Adam()\n",
    "loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True,\n",
    "                                                            reduction='none')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def loss_function(real, pred):\n",
    "    mask = tf.math.logical_not(tf.math.equal(real, 0))\n",
    "    loss_ = loss_object(real, pred)\n",
    "    mask = tf.cast(mask, dtype=loss_.dtype)\n",
    "    loss_ *= mask\n",
    "    return tf.reduce_mean(loss_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Checkpoint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "checkpoint_path = \"../models/image_caption/checkpoint/train\"\n",
    "ckpt = tf.train.Checkpoint(encoder=encoder,\n",
    "                           decoder=decoder,\n",
    "                           optimizer=optimizer)\n",
    "ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "start_epoch = 0\n",
    "if ckpt_manager.latest_checkpoint:\n",
    "    start_epoch = int(ckpt_manager.latest_checkpoint.split('-')[-1])\n",
    "    ckpt.restore(ckpt_manager.latest_checkpoint)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 训练模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "loss_plot = []"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tf.function\n",
    "def train_step(img_tensor, target):\n",
    "    loss = 0\n",
    "    hidden = decoder.reset_states(batch_size=target.shape[0])\n",
    "    dec_input = tf.expand_dims([tokenizer.word_index['<start>']] *\n",
    "                               target.shape[0], 1)\n",
    "\n",
    "    with tf.GradientTape() as tape:\n",
    "        features = encoder(img_tensor)\n",
    "\n",
    "        for i in range(1, target.shape[1]):\n",
    "            predictions, hidden, _ = decoder(dec_input, features, hidden)\n",
    "            loss += loss_function(target[:, i], predictions)\n",
    "            dec_input = tf.expand_dims(target[:, i], 1)\n",
    "\n",
    "        train_loss = loss / int(target.shape[1])\n",
    "        trainable_variables = encoder.trainable_variables + decoder.trainable_variables\n",
    "        gradients = tape.gradient(loss, trainable_variables)\n",
    "        optimizer.apply_gradients(zip(gradients, trainable_variables))\n",
    "        return loss, train_loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "EPOCHS = 20"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for epoch in range(start_epoch, EPOCHS):\n",
    "    start = time.time()\n",
    "    total_loss = 0\n",
    "\n",
    "    for (batch, (img_tensor, target)) in enumerate(dataset):\n",
    "        batch_loss, t_loss = train_step(img_tensor, target)\n",
    "        total_loss += t_loss\n",
    "\n",
    "        if batch % 100 == 0:\n",
    "            print(\"Epoch {} Batch {} Loss {:.4f}\".format(\n",
    "                epoch + 1, batch,\n",
    "                batch_loss.numpy() / int(target.shape[1])))\n",
    "        loss_plot.append(total_loss / num_steps)\n",
    "\n",
    "        if epoch % 5 == 0:\n",
    "            ckpt_manager.save()\n",
    "\n",
    "        print(\"Epoch {} Loss {:.6f}\".format(epoch + 1, total_loss / num_steps))\n",
    "        print(\"Time taken for 1 epoch {} sec\\n\".format(time.time() - start))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(loss_plot)\n",
    "plt.xlabel(\"Epochs\")\n",
    "plt.ylabel(\"Loss\")\n",
    "plt.title(\"Loss Plot\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 验证模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate(image):\n",
    "    attention_plot = np.zeros((max_length, attention_features_shape))\n",
    "    hidden = decoder.reset_states(batch_size=1)\n",
    "    temp_input = tf.expand_dims(load_image(image)[0], 0)\n",
    "    image_tensor_val = image_features_extract_model(temp_input)\n",
    "    image_tensor_val = tf.reshape(\n",
    "        image_tensor_val,\n",
    "        (image_tensor_val.shape[0], -1, image_tensor_val.shape[3]))\n",
    "    features = encoder(image_tensor_val)\n",
    "    dec_input = tf.expand_dims([tokenizer.word_index['<start>']], 0)\n",
    "    result = []\n",
    "\n",
    "    for i in range(max_length):\n",
    "        predictions, hidden, attention_weights = decoder(\n",
    "            dec_input, features, hidden)\n",
    "        attention_plot[i] = tf.reshape(attention_weights, (-1, )).numpy()\n",
    "        predicted_id = tf.random.categorical(predictions, 1)[0][0].numpy()\n",
    "        result.append(tokenizer.index_word[predicted_id])\n",
    "\n",
    "        if tokenizer.index_word[predicted_id] == '<end>':\n",
    "            return result, attention_plot\n",
    "\n",
    "        dec_input = tf.expand_dims([predicted_id], 0)\n",
    "\n",
    "    attention_plot = attention_plot[:len(result), :]\n",
    "    return result, attention_plot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_attention(image, result, attention_plot):\n",
    "    temp_image = np.array(Image.open(image))\n",
    "    fig = plt.figure(figsize=(10, 10))\n",
    "    len_result = len(result)\n",
    "\n",
    "    for l in range(len_result):\n",
    "        temp_att = np.resize(attention_plot[l], (8, 8))\n",
    "        ax = fig.add_subplot(len_result // 2, len_result // 2, l + 1)\n",
    "        ax.set_title(result[l])\n",
    "        img = ax.imshow(temp_image)\n",
    "        ax.imshow(temp_att, cmap='gray', alpha=0.6, extent=img.get_extent())\n",
    "    plt.tight_layout()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rid = np.random.randint(0, len(img_name_val))\n",
    "image = img_name_val[rid]\n",
    "real_caption = ' '.join([tokenizer.index_word[i] for i in cap_val[rid] if i not in [0]])\n",
    "result, attention_plot = evaluate(image)\n",
    "\n",
    "print ('Real Caption:', real_caption)\n",
    "print ('Prediction Caption:', ' '.join(result))\n",
    "plot_attention(image, result, attention_plot)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "image_url = 'https://tensorflow.org/images/surf.jpg'\n",
    "image_extension = image_url[-4:]\n",
    "image_path = tf.keras.utils.get_file('image'+image_extension,\n",
    "                                     origin=image_url)\n",
    "\n",
    "result, attention_plot = evaluate(image_path)\n",
    "print ('Prediction Caption:', ' '.join(result))\n",
    "plot_attention(image_path, result, attention_plot)\n",
    "# opening the image\n",
    "Image.open(image_path)"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "notebook_metadata_filter": "-all",
   "text_representation": {
    "extension": ".py",
    "format_name": "light"
   }
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
